package service.configuration.xstream;

import java.io.InputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;

import org.springframework.stereotype.Service;

import service.configuration.AssociationType;
import service.configuration.FieldType;
import service.configuration.IApplicationConfig;
import service.configuration.IAsofConfig;
import service.configuration.IAssociationConfig;
import service.configuration.IBlockConfig;
import service.configuration.IConfiguration;
import service.configuration.IFieldConfig;
import service.configuration.IFieldDateConfig;
import service.configuration.IFieldEnumConfig;
import service.configuration.IFieldNumberConfig;
import service.configuration.IFieldStringConfig;
import service.configuration.IFieldTextConfig;
import service.configuration.ILinkAssociationConfig;
import service.configuration.ILinkAssociationUpConfig;
import service.configuration.IOwnerAssociationConfig;
import service.configuration.IPersonSheetConfig;
import service.configuration.ISheetConfig;
import service.configuration.ITreeAssociationConfig;
import service.configuration.ITreeAssociationQueryConfig;
import service.configuration.NamingUtil;
import service.configuration.xstream.converter.TreeQueryConverter;
import service.configuration.xstream.converter.OwnerConverter;
import service.configuration.xstream.converter.LinkUpConverter;

import com.thoughtworks.xstream.XStream;
import com.thoughtworks.xstream.io.xml.DomDriver;

@Service
public class Configuration implements IConfiguration {
	private final AsofConfig config;
	private final List<String> allSheetTypes;
	private final List<ISheetConfig> allSheetConfigs;
	private final Map<String, String> sheetLabelByType;
	private final Map<String, ISheetConfig> sheetConfigs;
	private final Map<String, Map<String, IFieldConfig>> fieldConfigs;
	private final Map<String, List<String>> shortDisplayFields;
	private final Map<String, List<IAssociationConfig>> associationConfigs;
	private final Map<String, List<String>> childrenTypes;
	
	public Configuration() {
		
		XStream xstream = new XStream(new DomDriver());
		xstream.processAnnotations(AsofConfig.class);
		xstream.processAnnotations(BlockConfig.class);
		xstream.processAnnotations(SheetConfig.class);
		xstream.addDefaultImplementation(AssociationConfig.class, IAssociationConfig.class);
		xstream.addDefaultImplementation(BlockConfig.class, IBlockConfig.class);
		xstream.addDefaultImplementation(FieldConfig.class, IFieldConfig.class);
		xstream.addDefaultImplementation(PersonSheetConfig.class, IPersonSheetConfig.class);
		xstream.addDefaultImplementation(SheetConfig.class, ISheetConfig.class);
		xstream.addDefaultImplementation(FieldDateConfig.class, IFieldDateConfig.class);
		xstream.addDefaultImplementation(FieldNumberConfig.class, IFieldNumberConfig.class);
		xstream.addDefaultImplementation(FieldStringConfig.class, IFieldStringConfig.class);
		xstream.addDefaultImplementation(FieldTextConfig.class, IFieldTextConfig.class);
		xstream.addDefaultImplementation(FieldEnumConfig.class, IFieldEnumConfig.class);
		xstream.addDefaultImplementation(ApplicationConfig.class, IApplicationConfig.class);
		xstream.addDefaultImplementation(OwnerAssociationConfig.class, IOwnerAssociationConfig.class);
		xstream.addDefaultImplementation(TreeAssociationConfig.class, ITreeAssociationConfig.class);
		xstream.addDefaultImplementation(TreeAssociationQueryConfig.class, ITreeAssociationQueryConfig.class);
		xstream.addDefaultImplementation(LinkAssociationConfig.class, ILinkAssociationConfig.class);
		xstream.addDefaultImplementation(LinkAssociationUpConfig.class, ILinkAssociationUpConfig.class);
		xstream.registerConverter(new OwnerConverter());
		xstream.registerConverter(new LinkUpConverter());
		xstream.registerConverter(new TreeQueryConverter());
		xstream.addImplicitCollection(FieldEnumConfig.class, "values", "value", String.class);
		InputStream stream = Configuration.class.getResourceAsStream("../AsofConfig.xml");
		config = (AsofConfig)xstream.fromXML(stream);
		
		allSheetConfigs = agregateAllSheets(config);
		affectFieldNames(allSheetConfigs);
		affectFieldTypes(allSheetConfigs);
		addPersonFirstBlock((PersonSheetConfig)config.getPersonSheetConfig());
		allSheetTypes = generateSheetTypesList(allSheetConfigs);
		shortDisplayFields = generateShortDisplayFieldsMap(allSheetConfigs);
		fieldConfigs = generateFieldsMap(allSheetConfigs);
		sheetConfigs = generateSheetsMap(allSheetConfigs);
		sheetLabelByType = generateTypeList(sheetConfigs);
		associationConfigs = generateAssociationsMap(config);
		childrenTypes = generateChidlrenTypes(config, allSheetTypes);
	}

	private void affectFieldNames(FieldConfig field) {
		if (field.getName() == null) {
			field.setName(field.getColumn());
		}
	}
	
	private void affectFieldNames(List<ISheetConfig> allSheetConfigs) {
		for (ISheetConfig sheet : allSheetConfigs) {
			for (IBlockConfig block : sheet.getBlockConfigs()) {
				for (IFieldConfig field : block.getFieldConfigs()) {
					affectFieldNames((FieldConfig)field);
				}
			}
		}
	}
	
	private void affectFieldTypes(FieldConfig field) {
		if (field.getDate() != null) {
			field.setType(FieldType.date);
			field.setTypeConfig(field.getDate());
		} else if (field.getText() != null) {
			field.setType(FieldType.text);
			field.setTypeConfig(field.getText());
		} else if (field.getString() != null) {
			field.setType(FieldType.string);
			field.setTypeConfig(field.getString());
		} else if (field.getNumber() != null) {
			field.setType(FieldType.number);
			field.setTypeConfig(field.getNumber());
		} else if (field.getEnumeration() != null) {
			field.setType(FieldType.enumaration);
			field.setTypeConfig(field.getEnumeration());
		} else {
			field.setType(FieldType.string);
		}
	}
	
	private void affectFieldTypes(List<ISheetConfig> allSheetConfigs) {
		for (ISheetConfig sheet : allSheetConfigs) {
			for (IBlockConfig block : sheet.getBlockConfigs()) {
				for (IFieldConfig field : block.getFieldConfigs()) {
					affectFieldTypes((FieldConfig)field);
				}
			}
		}
	}
	
	private void addPersonFirstBlock(PersonSheetConfig person) {
		BlockConfig block = new BlockConfig("Civilités", new ArrayList<IFieldConfig>());
		block.addFieldConfig(
				new FieldConfig(NamingUtil.FIRSTNAME, person.getFirstName(),
						FieldType.string, "Prénom", "", false, true, false, true));
		block.addFieldConfig(
				new FieldConfig(NamingUtil.LASTNAME, person.getLastName(), 
						FieldType.string, "Nom", "", false, true, false, true));
		block.addFieldConfig(
				new FieldConfig(NamingUtil.MAIL, person.getMail(), 
						FieldType.string, "mail", "", false, true, false, false));
		block.addFieldConfig(
				new FieldConfig(NamingUtil.LDAP, person.getLdap(), 
						FieldType.string, "ldap", "", false, true, false, false));
		
		person.getBlockConfigs().add(0, block);
	}
	
	private Map<String, ISheetConfig> generateSheetsMap(List<ISheetConfig> allSheetConfigs) {
		Map<String, ISheetConfig> sheets = new HashMap<String, ISheetConfig>();
		
		for (ISheetConfig conf : allSheetConfigs) {
			sheets.put(conf.getName(), conf);
		}
		return sheets;
	}
	
	private Map<String, List<IAssociationConfig>> generateAssociationsMap(IAsofConfig config) {
		
		Map<String, List<IAssociationConfig>> associationConfigs = new TreeMap<String, List<IAssociationConfig>>();
		
		if (config.getAssociationConfigs() == null) {
			AsofConfig aconfig = (AsofConfig)config;
			aconfig.setAssociationConfigs(new ArrayList<IAssociationConfig>());
			for(IOwnerAssociationConfig owner : aconfig.getOwnerAssociations()) {
				aconfig.getAssociationConfigs().add(
						new AssociationConfig(AssociationType.owner, 
						config.getPersonSheetConfig().getName(), 
						owner.getTo(), owner.getQuery(), null));
			}
			for(ITreeAssociationConfig tree : aconfig.getTreeAssociations()) {
				for(ITreeAssociationQueryConfig query : tree.getQuery())  {
				aconfig.getAssociationConfigs().add(
						new AssociationConfig(AssociationType.tree, 
						tree.getFrom(), query.getTo(), query.getQuery(), null));
				}
			}
			for(ILinkAssociationConfig link : aconfig.getLinkAssociations()) {
				for(ILinkAssociationUpConfig up : link.getUp())  {
				aconfig.getAssociationConfigs().add(
						new AssociationConfig(AssociationType.link, 
						link.getFrom(), up.getTo(), up.getQuery(), up.getLabel()));
				}
			}
		}
		
		for(IAssociationConfig association : config.getAssociationConfigs()) {
			String key = association.getType() + association.getFrom() +association.getTo();
			if (! associationConfigs.containsKey(key)) {
				associationConfigs.put(key, new ArrayList<IAssociationConfig>());
			}
			associationConfigs.get(key).add(association);
		}
		
		return associationConfigs;
	}
	
	private Map<String, String> generateTypeList(Map<String, ISheetConfig> sheetConfigs) {
		Map<String, String> types = new TreeMap<String, String>();
		for (String key : sheetConfigs.keySet()) {
			types.put(key, sheetConfigs.get(key).getLabel());
		}
		return types;
	}
	
	private Map<String, IFieldConfig> generateFieldsMap(ISheetConfig sheetConf) {
		Map<String, IFieldConfig> fields = new HashMap<String, IFieldConfig>();
		for(IBlockConfig block : sheetConf.getBlockConfigs()) {
			for(IFieldConfig field : block.getFieldConfigs()) {
				fields.put(field.getName(), field);
			}
		}
		return fields;
	}
	
	private Map<String, Map<String, IFieldConfig>> generateFieldsMap(
			List<ISheetConfig> allSheetConfigs) {
		
		Map<String, Map<String, IFieldConfig>> fields = 
				new HashMap<String, Map<String, IFieldConfig>>();

		for (ISheetConfig conf : allSheetConfigs) {
			fields.put(conf.getName(), generateFieldsMap(conf));
		}
		
		return fields;
	}
	
	private List<String> generateShortDisplayFieldsMap(ISheetConfig sheetConf) {
		List<String> fields = new ArrayList<String>();
		for(IBlockConfig block : sheetConf.getBlockConfigs()) {
			for(IFieldConfig field : block.getFieldConfigs()) {
				if (field.isInShortDisplay()) {
					fields.add(field.getName());
				}
			}
		}
		return fields;
	}
	
	private Map<String, List<String>> generateShortDisplayFieldsMap(
			List<ISheetConfig> allSheetConfigs) {
		
		Map<String, List<String>> fields = new HashMap<String, List<String>>();

		for (ISheetConfig conf : allSheetConfigs) {
			fields.put(conf.getName(), generateShortDisplayFieldsMap(conf));
		}
		return fields;
	}
	
	private List<ISheetConfig> agregateAllSheets(IAsofConfig config) {
		List<ISheetConfig> sheets = new ArrayList<ISheetConfig>();
		IPersonSheetConfig person = config.getPersonSheetConfig();
		if (person != null) {
			sheets.add(person);
		}
		if (config.getSheetConfigs() != null) {
			sheets.addAll((List<ISheetConfig>)config.getSheetConfigs());
		}
		return sheets;
	}
	
	private List<String> generateSheetTypesList(List<ISheetConfig> allSheetConfigs) {
		List<String> types = new ArrayList<String>();
		for(ISheetConfig conf : allSheetConfigs) {
			types.add(conf.getName());
		}
		return types;
	}
	
	
	private Map<String, List<String>> generateChidlrenTypes(IAsofConfig config, List<String> allSheetTypes) {
		Map<String, List<String>> childrenTypes = new HashMap<String, List<String>>();
		for(String type : allSheetTypes) {
			childrenTypes.put(type, new ArrayList<String>());
		}
		
		if (config.getAssociationConfigs() != null) {
			for(IAssociationConfig association : config.getAssociationConfigs()) {
				childrenTypes.get(association.getFrom()).add(association.getTo());
			}
		}
		
		return childrenTypes;
	}
	
	/** 
	 * Cette méthode ne fait presque aucun calcul.
	 * Elle renvoie la configuration tel que chargée depuis le fichier de configuration.
	 * 
	 * @return	AsofConfig   une instance de la classe AsofConfig
	 */
	public IAsofConfig getConfig() {
		return (IAsofConfig) config;
	}
	
	/** 
	 * Cette méthode ne fait presque aucun calcul. 
	 * Elle utilise une map généré à la première instanciation du service.
	 * 
	 * @param	type         le type de la fiche
	 * @return	sheetConfig  une instance de la classe SheetConfig 
	 *                       correspondante au type fournit en paramètre.
	 */
	public ISheetConfig getSheetConfig(String type) {
		return sheetConfigs.get(type);
	}
	

	/** 
	 * Cette méthode ne fait aucun calcul. 
	 * Elle retourne une map généré à la première instanciation du service.
	 * 
	 * @return	sheetTypes  une map dont la clef est le type d'un fiche et la valeur son label.
	 */
	public Map<String, String> getSheetLabelByType() {
		return sheetLabelByType;
	}
	
	/** 
	 * Cette méthode ne fait presque aucun calcul. 
	 * Elle utilise une map généré à la première instanciation du service.
	 * 
	 * @param	sheetType         le type de la fiche
	 * @param	fieldName         le nom de la colonne du champs recherché
	 * @return	FieldConfig  une instance de la classe FieldConfig 
	 *                       correspondante au type de fiche et au champs recherché.
	 */
	public IFieldConfig getFieldConfig(String sheetType, String fieldName) {
		return fieldConfigs.get(sheetType).get(fieldName);
	}
	
	/** 
	 * Cette méthode ne fait aucun calcul. 
	 * 
	 * @return	La chaine de caractère représentant le type personne
	 */
	public String getPersonType() {
		return config.getPersonSheetConfig().getName();
	}
	
	/** 
	 * Cette méthode ne fait presque aucun calcul. 
	 * Elle utilise une map généré à la première instanciation du service.
	 * 
	 * @param	sheetType         le type de la fiche
	 * @return	La liste des champs (leur clef ou colonne) qui apparaissent dans l'affichage court
	 */
	public List<String> getShortDisplayFieldKeys(String sheetType) {
		return shortDisplayFields.get(sheetType);
	}
	
	/** 
	 * Cette méthode ne fait aucun calcul. 
	 * 
	 * @return	La liste de toute les fiches y compris la fiche "SheetPerson"
	 */
	public List<ISheetConfig> getAllSheetConfigs() {
		return allSheetConfigs;
	}

	@Override
	public IAssociationConfig getFirstAssociationConfig(AssociationType type, String fromType,
			String toType) {
		List<IAssociationConfig> list = associationConfigs.get(type + fromType + toType);
		return list == null || list.isEmpty() ? null : list.get(0);
	}
	
	@Override
	public List<IAssociationConfig> getAssociationConfigs(AssociationType type, String fromType,
			String toType) {
		return associationConfigs.get(type + fromType + toType);
	}

	@Override
	public List<String> getSheetTypes() {
		return allSheetTypes;
	}
	
	
	public List<String> getChildrenTypes(String type) {
		return childrenTypes.get(type);
	}
	
	public IApplicationConfig getApplicationConfig() {
		return config.getApplicationConfig();
	}


	public String getApplicationUrl() {
		return config.getApplicationConfig().getUrl();
	}
	
	public boolean isApplicationDebug() {
		return config.getApplicationConfig().isDebug();
	}

	public String getApplicationAdminMail() {
		return config.getApplicationConfig().getAdminMail();
	}
}
